package com.xwj.seckill.aspect;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;

import java.lang.reflect.Field;
import java.util.Collections;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * 将加锁操作放到切面中，确保事务提交后释放锁
 * 仍然超卖，有待优化
 *
 * @author xwj
 * @date 2020/5/6
 */
@Component
@Aspect
@Order(1)
public class RedisServiceLockAspect {
    @Autowired
    private RedisTemplate redisTemplate;

    @Pointcut("@annotation(com.xwj.seckill.annotation.RedisServiceLock)")
    public void pointcut() {
    }

    @Around("pointcut()")
    public Object around(ProceedingJoinPoint joinPoint) {
        Object obj = null;
        Long userId = null;
        String bookNo = null;
        Object[] args = joinPoint.getArgs();
        try {
            for (Object o : args) {
                Field field = o.getClass().getDeclaredField("userId");
                if (field != null) {
                    field.setAccessible(true);
                    Object object = field.get(o);
                    if (object != null) {
                        userId = (Long) object;
                    }
                }
                field = o.getClass().getDeclaredField("bookNo");
                if (field != null) {
                    field.setAccessible(true);
                    Object object = field.get(o);
                    if (object != null) {
                        bookNo = (String) object;
                    }
                }
            }
        } catch (Exception e) {
        }
        final String key = "lock:" + bookNo;
        final String value = UUID.randomUUID().toString() + ":" + bookNo;
        // 以下方式最low，不推荐使用
        ValueOperations operations = redisTemplate.opsForValue();
        boolean result = false;
        try {
            // 调用SETNX K V NX PX命令操作获得锁
            result = operations.setIfAbsent(key, value, 5L, TimeUnit.SECONDS);
            if (result) {
                obj = joinPoint.proceed();
            }
        } catch (Throwable e) {
            e.printStackTrace();
            throw new RuntimeException();
        } finally {
            if (result) {
                // 释放锁
                String release_lock_lua_script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
                DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(release_lock_lua_script, Long.class);
                redisTemplate.execute(redisScript, Collections.singletonList(key), Collections.singletonList(value));
            }
        }
        return obj;
    }
}
